using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.MSAS.ManagedAPI;
using WEX.Logging.Interop;

namespace Microsoft.Reputation.Tests.FrontEndCommon
{
    public class UrfFileParser
    {
        private static readonly uint S_OK = 0;
        //private static uint NO_MATCH = 4098;

        public static readonly int CONFIDENCE_YES = 1;
        public static readonly int CONFIDENCE_NO = 2;
        public static readonly int CONFIDENCE_UNKNOWN = 3;
        public static readonly int CONFIDENCE_ERROR = 4;

        public UrfFileParser() { }

        ~UrfFileParser() { }

        public static List<UrfReputationObject> GetUrfObjects(string filePath)
        {
            List<UrfReputationObject> objList = new List<UrfReputationObject>();
            using (StreamReader urfFile = File.OpenText(filePath))
            {
                string line = String.Empty;
                while ((line = urfFile.ReadLine()) != null)
                {
                    if (!line.StartsWith("#"))
                    {
                        objList.Add(UrfReputationObject.ReadToReputationObject(line, 0));
                    }
                }
            }
            return objList;
        }

        public static List<UrfReputationObject> GetUrfObjects(string filePath, long version)
        {
            List<UrfReputationObject> objList = new List<UrfReputationObject>();
            using (StreamReader urfFile = File.OpenText(filePath))
            {
                string line = String.Empty;
                while ((line = urfFile.ReadLine()) != null)
                {
                    objList.Add(UrfReputationObject.ReadToReputationObject(line, version));
                }
            }
            return objList;
        }

        public static string ConvertLookupResponseToURF(IList<MrsLookupResponse> responses, string filePath)
        {
            if (responses == null)
            {
                Console.WriteLine("MrsLookupResponse Object is Empty, exiting...");
                return String.Empty;
            }

            string bloom = null;
            string hash = null;

            FileStream fS = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
            StreamWriter urfFile = new StreamWriter(fS);

            foreach (MrsLookupResponse resp in responses)
            {
                //This is to handle any No-Matches that we get. Instead of specifically handling the NO_MATCH error code (4098), we are skipping for anything other than S_OK.
                if (!uint.Equals(S_OK, resp.Status))
                {
                    continue;
                }
                else
                {

                    foreach (MrsReputationObject repuObj in resp.Matches)
                    {
                        MrsMetadata bloomObj = repuObj.FindMetadataForSearchField("bloom");
                        MrsMetadata sha256Obj = repuObj.FindMetadataForSearchField("sha256");

                        if (bloomObj == null) { Console.WriteLine("Guess what, the Bloom is NULL."); }
                        if (sha256Obj == null) { Console.WriteLine("Guess what, the sha256Obj is NULL."); }

                        bloom = (bloomObj == null) ? "000000000000000000000000000000000000000000000000000000000000000000" : Convert.ToBase64String((byte[])bloomObj.Value); // Bloom can be NULL incase of a No Match, so checking for that.
                        //bloom = (bloomObj == null) ? "7F0000000000000000000000000000000000000000000000000000000000000000" : Convert.ToBase64String((byte[])bloomObj.Value); // Bloom can be NULL incase of a No Match, so checking for that.
                        hash = Convert.ToBase64String((byte[])sha256Obj.Value);

                        urfFile.Write("\t");
                        urfFile.Write("{0}\t{1}\t",
                                    hash,
                                    bloom);

                        if (repuObj.Version == Int32.MinValue)
                        {
                            urfFile.WriteLine();
                            TelemetryHelper.DisplayWarning("Found a NO MATCH, ignoring the CategoryReputation writeline and continuing");
                            continue;
                        }
                        else
                        {
                            IList<MrsCategoryReputation> rInfo = repuObj.Reputation;
                            if (null == rInfo)
                            {
                                urfFile.WriteLine();
                                TelemetryHelper.DisplayWarning("CategoryReputation is empty, ignoring, writeline and continuing");
                                continue;
                            }

                            bool firstCategory = true;
                            foreach (MrsCategoryReputation rData in rInfo)
                            {
                                if (!firstCategory)
                                {
                                    urfFile.Write(",");
                                }
                                firstCategory = false;
                                urfFile.Write("{0}:{1:000}:", rData.CategoryId, rData.Confidence);

                                bool firstInheritance = true;
                                foreach (MrsMetadata catData in rData.CategoryInfo)
                                {
                                    if (!firstInheritance)
                                    {
                                        urfFile.Write(":");
                                    }
                                    firstInheritance = false;
                                    urfFile.Write("{0}", catData.Value);
                                }
                            }
                        }
                        urfFile.WriteLine();
                    }

                }
            }
 
            urfFile.Flush();
            urfFile.Close();
            fS.Close();
            return fS.Name;
        }

        #region Conversion of URF to Simple Reputation Data Array

        /// <summary>
        /// 
        ///    Convert an URF file to SimpleReputationData[].
        ///    
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static SimpleReputationData[] ConvertURFtoSimpleReputationData(string file) 
        {

            if (string.IsNullOrEmpty(file))
            {
                throw new ArgumentNullException(file, "File path cannot be null or empty");
            }

            IList<SimpleReputationData> retList = new List<SimpleReputationData>(19);

            string sha256;
            string bloom;
            List<InputCategory> categories;

            try
            {
                StreamReader urfFile = File.OpenText(file);

                string line = String.Empty;
                int lineCount = 0;
                while ((line = urfFile.ReadLine()) != null)
                {
                    lineCount++;

                    if (UrfLineParser(line, out sha256, out bloom, out categories))
                    {
                        //SimpleReputationData srd = new SimpleReputationData(sha256, bloom, categories);
                        SimpleReputationData srd = new SimpleReputationData();
                        srd.sha256 = HexStringToByteArray(sha256);
                        srd.bloom = HexStringToByteArray(bloom);
                        srd.categories = (categories != null) ? categories.ToArray() : null;

                        retList.Add(srd);
                    }
                }

            }
            catch (FileNotFoundException e)
            {
                Log.Comment("Operation cannot be completed", e.Message);
                throw;
            }

            return retList.ToArray();
        }

        /// <summary>
        /// 
        ///    Extract sha, bloom and categories from a URF file line.
        ///    
        /// </summary>
        /// <param name="line"></param>
        /// <param name="sha256"></param>
        /// <param name="bloom"></param>
        /// <param name="categories"></param>
        /// <returns></returns>
        private static bool UrfLineParser(    string line, 
                                          out string sha256, 
                                          out string bloom, 
                                          out List<InputCategory> categories)
        {

            //Console.WriteLine("UrfLineParser line={" + line);

            // Adding support for comments
            if (line.StartsWith("#"))
            {
                sha256 = string.Empty;
                bloom = string.Empty;
                categories = null;
                return false;
            }

            string[] parsedLine = line.Split('\t');

            if (parsedLine.Length != 4)
            {
                throw new Exception("The Input test file doesnt have the right number of tabs, the problematic line is: " + line); ;
            }

            //parsedLine[0] is url and not needed
            sha256 = parsedLine[1];
            bloom = parsedLine[2];
            categories = null;

            if (false == string.IsNullOrEmpty(parsedLine[3]))
            {
                string[] catInfo = parsedLine[3].Split(',');
                //Console.WriteLine("catinfo.length={" + catInfo.Length + "}");

                categories = new List<InputCategory>(catInfo.Length);
                string[] repuData;
                bool validInputCategoryFound = false;

                foreach (string cInfo in catInfo)
                {
                    repuData = cInfo.Split(':');
                    if (4 == repuData.Length)
                    {
                        validInputCategoryFound = true;
                        InputCategory ic = new InputCategory();
                        ic.categoryId = uint.Parse(repuData[0]);
                        ic.confidence = byte.Parse(repuData[1]);
                        ic.pathInheritance = (int.Parse(repuData[2]) == 1) ? true : false;
                        ic.hostInheritance = (int.Parse(repuData[3]) == 1) ? true : false;
                        categories.Add(ic);
                    }
                }

                if (!validInputCategoryFound)
                {
                    categories = null;
                }
            }

            return true;
        }

        #endregion 

        #region Byte Array operations

        /// <summary>
        /// 
        ///    Convert a HexString to Byte array.
        ///    
        /// An HexString is a string with each byte represented by two chars (in hex), eg.
        /// 
        /// AC864020
        /// 
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static byte[] HexStringToByteArray(string s)
        {
            char[] chars = s.ToCharArray();
            byte[] bytes = new byte[chars.Length / 2];

            for (int i = 0; i < chars.Length; )
            {
                string sub = s.Substring(i, 2);
                bytes[i / 2] = System.Convert.ToByte(sub, 16);
                i += 2;
            }
            return bytes;
        }

        /// <summary>
        /// 
        ///   lho: data block 
        ///   rho: data block 
        ///   
        ///   Adapted from ClientCategorizeProcessorTest::DataBlocksEqual()
        ///   
        /// </summary>
        /// <param name="expected"></param>
        /// <param name="actual"></param>
        /// <returns></returns>
        public static bool CompareDataBlocks(byte[] lho, byte[] rho)
        {
            if ((null == lho) && (null == rho))
            {
                return true;
            }

            if ((null == lho && null != rho)  ||
                (null != lho && null == rho)   )
            {
                return false;
            }

            if ((rho.Length != lho.Length))
            {
                return false;
            }

            //Console.WriteLine("Data block length = {" + rho.Length + "}");
            //Console.WriteLine(Convert.ToBase64String(expected));
            //Console.WriteLine(Convert.ToBase64String(actual));            
            return Convert.ToBase64String(lho).Equals(Convert.ToBase64String(rho));
        }

        /// <summary>
        /// 
        ///    Convert reputation from byte to int value used by Urf File
        ///    
        /// </summary>
        /// <param name="reput"></param>
        /// <returns></returns>
        public static int ConvertConfidence(byte reputataion)
        {
            if (ClientCategorizeProcessor.Conf100 == reputataion)
            {
                return CONFIDENCE_YES;
            }
            else if (ClientCategorizeProcessor.Conf0 == reputataion)
            {
                return CONFIDENCE_NO;
            }
            else if (ClientCategorizeProcessor.Conf50 == reputataion)
            {
                return CONFIDENCE_UNKNOWN;
            }
            else
            {
                return CONFIDENCE_ERROR;
            }                     
        }

        #endregion 
    }
}